package org.feisoft.jta.supports.dubbo.spi;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.cluster.LoadBalance;
import org.apache.commons.lang3.StringUtils;
import org.feisoft.jta.TransactionImpl;
import org.feisoft.jta.supports.dubbo.InvocationContext;
import org.feisoft.jta.supports.dubbo.InvocationContextRegistry;
import org.feisoft.jta.supports.dubbo.TransactionBeanRegistry;
import org.feisoft.jta.supports.dubbo.ext.ILoadBalancer;
import org.feisoft.transaction.TransactionBeanFactory;
import org.feisoft.transaction.TransactionManager;
import org.feisoft.transaction.archive.XAResourceArchive;
import org.feisoft.transaction.supports.resource.XAResourceDescriptor;
import org.springframework.core.env.Environment;

import java.util.List;

public final class TransactionLoadBalance implements LoadBalance
{
    static final String CONSTANT_LOADBALANCE_KEY = "org.feisoft.jta.loadbalance";

    private ILoadBalancer loadBalancer;

    private void fireInitializeIfNecessary()
    {
        if (this.loadBalancer == null)
        {
            this.initializeIfNecessary();
        }
    }

    private synchronized void initializeIfNecessary()
    {
        if (this.loadBalancer == null)
        {
            Environment environment = TransactionBeanRegistry.getInstance().getEnvironment();
            String loadBalanceKey = environment.getProperty(CONSTANT_LOADBALANCE_KEY, "default");
            ExtensionLoader<ILoadBalancer> extensionLoader = ExtensionLoader.getExtensionLoader(ILoadBalancer.class);
            this.loadBalancer = extensionLoader.getExtension(loadBalanceKey);
        }
    }

    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException
    {
        InvocationContextRegistry registry = InvocationContextRegistry.getInstance();
        InvocationContext invocationContext = registry.getInvocationContext();
        if (invocationContext == null)
        {
            return this.selectConfigedInvoker(invokers, url, invocation);
        } else
        {
            return this.selectSpecificInvoker(invokers, url, invocation, invocationContext);
        }
    }

    public <T> Invoker<T> selectConfigedInvoker(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException
    {
        if (invokers == null || invokers.isEmpty())
        {
            throw new RpcException("No invoker is found!");
        }

        TransactionBeanFactory beanFactory = TransactionBeanRegistry.getInstance().getBeanFactory();
        TransactionManager transactionManager = beanFactory.getTransactionManager();
        TransactionImpl transaction = //
                (TransactionImpl) transactionManager.getTransactionQuietly();
        List<XAResourceArchive> participantList = transaction == null ? null : transaction.getRemoteParticipantList();

        for (int i = 0; invokers != null && participantList != null && participantList.isEmpty() == false
                && i < invokers.size(); i++)
        {
            Invoker<T> invoker = invokers.get(i);
            URL invokerUrl = invoker.getUrl();
            String invokerHost = invokerUrl.getHost();
            int invokerPort = invokerUrl.getPort();
            String invokerAddr = String.format("%s:%s", invokerHost, invokerPort);
            for (int j = 0; participantList != null && j < participantList.size(); j++)
            {
                XAResourceArchive archive = participantList.get(j);
                XAResourceDescriptor descriptor = archive.getDescriptor();
                String identifier = descriptor.getIdentifier();
                String[] values = identifier == null ? new String[0] : identifier.split("\\s*:\\s*");
                String targetAddr = values.length == 3 ? values[0] : null;
                String targetPort = values.length == 3 ? values[2] : null;
                String remoteAddr = String.format("%s:%s", targetAddr, targetPort);
                if (StringUtils.equalsIgnoreCase(invokerAddr, remoteAddr))
                {
                    return invoker;
                } // end-if (StringUtils.equalsIgnoreCase(invokerAddr, identifier))
            }
        }

        this.fireInitializeIfNecessary();

        if (this.loadBalancer == null)
        {
            throw new RpcException("No org.feisoft.jta.supports.dubbo.ext.ILoadBalancer is found!");
        } else
        {
            return this.loadBalancer.select(invokers, url, invocation);
        }

    }

    public <T> Invoker<T> selectSpecificInvoker(List<Invoker<T>> invokers, URL url, Invocation invocation,
                                                InvocationContext context) throws RpcException
    {
        String serverHost = context.getServerHost();
        int serverPort = context.getServerPort();
        for (int i = 0; invokers != null && i < invokers.size(); i++)
        {
            Invoker<T> invoker = invokers.get(i);
            URL targetUrl = invoker.getUrl();
            String targetAddr = targetUrl.getIp();
            int targetPort = targetUrl.getPort();
            if (StringUtils.equals(targetAddr, serverHost) && targetPort == serverPort)
            {
                return invoker;
            }
        }
        throw new RpcException(String.format("Invoker(%s:%s) is not found!", serverHost, serverPort));
    }

}
